use Wx 0.15 qw[:allclasses];
use strict;
package wxSolutionDetails;

use Wx qw[:everything];
use base qw(Wx::Panel);
use strict;
use Storable;
use SetSolution;
use wxTreeWindow;
use wxBudgetSlider;
use wxSortableGrid;
use Scalar::Util qw(blessed);

use constant INDICES => { "Pendant\nedge" => 'PE',  "Equal\nsplits" => 'ES',  "Fair\nproportion" => 'FP', "Shapley\nvalue" => 'SHAPLEY'};
use constant SUMMARY => { "Maximum\nvalue" => 'Max', "Mean\nvalue" => 'Mean', "Minimum\nValue"=>'Min'};


sub new {
	my( $self, $parent, $id, $pos, $size, $style, $name ) = @_;
	    
	$parent = undef              unless defined $parent;
	$id     = -1                 unless defined $id;
	$pos    = wxDefaultPosition  unless defined $pos;
	$size   = wxDefaultSize      unless defined $size;
	$name   = ""                 unless defined $name;

	$style = wxDEFAULT_FRAME_STYLE 
		unless defined $style;

	#Create controls
	$self = $self->SUPER::new( $parent, $id, $pos, $size, $style, $name );
	
	$self->{splitter_window} = Wx::SplitterWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxSP_3D|wxSP_BORDER);
	$self->{LeftPanel} = Wx::Panel->new($self->{splitter_window}, -1, wxDefaultPosition, wxDefaultSize);
	$self->{RightPanel} = Wx::Panel->new($self->{splitter_window}, -1, wxDefaultPosition, wxDefaultSize);
	
	$self->{slider} = wxBudgetSlider->new($self->{LeftPanel}, -1, wxDefaultPosition, wxDefaultSize);
	
	$self->{grid_solutions} = wxSortableGrid->new($self->{LeftPanel}, -1, wxDefaultPosition, wxDefaultSize,wxRAISED_BORDER);
	#We set the default b/g colour to gray so that the area outside the grid is not drawn white
	#on some platforms this overwrites the grid line colour so we store and reset this here
	my $gl_colour = $self->{grid_solutions}->GetGridLineColour(); 
   	$self->{grid_solutions}->SetDefaultCellBackgroundColour(Wx::SystemSettings::GetSystemColour(wxSYS_COLOUR_3DFACE));	
	$self->{grid_solutions}->SetGridLineColour($gl_colour);

	$self->{wxtree} = wxTreeWindow->new($self->{RightPanel}, -1);

	#The budget combo
	$self->{budget_combo} = Wx::ComboBox->new($self->{LeftPanel}, -1, '', wxDefaultPosition, wxDefaultSize, ['Budget:','All Budgets'],wxCB_READONLY);
	$self->{budget_combo}->SetSelection(0);
	my $wxsize = $self->{budget_combo}->GetMinSize();
	$wxsize->SetWidth(120);
	$self->{budget_combo}->SetMinSize($wxsize);

	#The greedy traversal controls
	$self->{panel_greedy} = Wx::Panel->new($self->{LeftPanel}, -1, wxDefaultPosition, wxDefaultSize);
	$self->{box_greedy} = Wx::StaticBox->new($self->{panel_greedy}, -1, 'Greedy traversal');
	$self->{combo_add} = Wx::ComboBox->new($self->{panel_greedy}, -1, '', wxDefaultPosition, wxDefaultSize, [],wxCB_READONLY);
	$self->{combo_del} = Wx::ComboBox->new($self->{panel_greedy}, -1, '', wxDefaultPosition, wxDefaultSize, [],wxCB_READONLY);
	$self->{button_add} = Wx::Button->new($self->{panel_greedy}, -1, "Greedy Addition");
	$self->{button_del} = Wx::Button->new($self->{panel_greedy}, -1, "Greedy Deletion");

	#The index selection controls
	$self->{panel_select} = Wx::Panel->new($self->{LeftPanel}, -1, wxDefaultPosition, wxDefaultSize);
	$self->{box_select} = Wx::StaticBox->new($self->{panel_select}, -1, 'Show');
	$self->{check_index} = Wx::CheckBox->new($self->{panel_select}, -1, 'Species specific indices');
	$self->{check_summary} = Wx::CheckBox->new($self->{panel_select}, -1, 'Summary statistics');
	$self->{check_solution} = Wx::CheckBox->new($self->{panel_select}, -1, 'Summary solutions');
	$self->{check_index}->SetForegroundColour(wxRED);
	$self->{check_summary}->SetForegroundColour(wxGREEN);
	$self->{check_solution}->SetForegroundColour(wxBLUE);
	$self->{check_solution}->SetValue(1);

	#Do the layout
	$self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
	$self->{sizer_budget} = Wx::BoxSizer->new(wxHORIZONTAL);
	$self->{sizer_buttons} = Wx::BoxSizer->new(wxHORIZONTAL);
	$self->{sizer}->Add($self->{splitter_window}, 1, wxEXPAND, 0);

	#The greedy traversal button layout
	$self->{sizer_greedy_box} = Wx::StaticBoxSizer->new($self->{box_greedy}, wxHORIZONTAL);
	$self->{sizer_greedy} = Wx::GridSizer->new(2,2);
	$self->{sizer_greedy_box}->Add($self->{sizer_greedy},1,wxEXPAND);
	$self->{sizer_greedy}->Add($self->{combo_add},0,wxALIGN_CENTER_VERTICAL);
	$self->{sizer_greedy}->Add($self->{combo_del},0,wxALIGN_CENTER_VERTICAL);
	$self->{sizer_greedy}->Add($self->{button_add},0,wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER);
	$self->{sizer_greedy}->Add($self->{button_del},0,wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER);

	#The index selection layout
	$self->{sizer_select} = Wx::StaticBoxSizer->new($self->{box_select}, wxVERTICAL);
	$self->{sizer_select}->Add($self->{check_index},1,wxEXPAND);
	$self->{sizer_select}->Add($self->{check_summary},1,wxEXPAND);
	$self->{sizer_select}->Add($self->{check_solution},1,wxEXPAND);


	$self->{sizer_buttons}->AddStretchSpacer(1);
	$self->{sizer_buttons}->Add($self->{panel_greedy},0,wxALIGN_LEFT);
	$self->{sizer_buttons}->AddStretchSpacer(3);
	$self->{sizer_buttons}->Add($self->{panel_select},0,wxALIGN_RIGHT);
	$self->{sizer_buttons}->AddStretchSpacer(1);

	$self->{sizer_top} = Wx::BoxSizer->new(wxVERTICAL);
	$self->{sizer_bottom} = Wx::BoxSizer->new(wxVERTICAL);
	$self->{sizer_budget}->Add($self->{budget_combo}, 0, wxEXPAND, 0 );
	$self->{sizer_budget}->Add($self->{slider}, 1, wxEXPAND, 0);
	$self->{sizer_top}->Add($self->{sizer_budget}, 0, wxEXPAND, 0);
	$self->{sizer_top}->Add($self->{grid_solutions}, 1, wxEXPAND, 0);
	$self->{sizer_top}->Add($self->{sizer_buttons}, 0, wxALIGN_CENTER|wxEXPAND, 0);
	$self->{sizer_bottom}->Add($self->{wxtree}, 30, wxEXPAND, 0);
	$self->{splitter_window}->SplitHorizontally($self->{LeftPanel}, $self->{RightPanel}, 200);
	
	$self->{splitter_window}->SetMinimumPaneSize(200);
	
	$self->SetSizer($self->{sizer});
	$self->{panel_greedy}->SetSizer($self->{sizer_greedy_box});
	$self->{panel_select}->SetSizer($self->{sizer_select});
	$self->{LeftPanel}->SetSizer($self->{sizer_top});
	$self->{RightPanel}->SetSizer($self->{sizer_bottom});
	#not really necessary -> but leaving for future reference
	$self->{sizer}->Fit($self);
	
	$self->Layout();
	
	#Register events
	$self->{grid_solutions}->set_selection_handler($self);
	Wx::Event::EVT_COMMAND_SCROLL($self, $self->{slider}->GetId, \&on_grid_data_change);
	Wx::Event::EVT_BUTTON($self, $self->{button_add}->GetId, \&greedy_addition);
	Wx::Event::EVT_BUTTON($self, $self->{button_del}->GetId, \&greedy_deletion);	
	Wx::Event::EVT_COMBOBOX($self, $self->{budget_combo}->GetId, \&budget_combo_select);
	Wx::Event::EVT_CHECKBOX($self, $self->{check_index}->GetId, \&fill_grid);
	Wx::Event::EVT_CHECKBOX($self, $self->{check_summary}->GetId, \&fill_grid);
	Wx::Event::EVT_CHECKBOX($self, $self->{check_solution}->GetId, \&fill_grid);
	$self->{display_indices} = 1;
	$self->{display_summary} = 1;
	$self->{display_solutions} = 1;
	
	return $self;

}

#Set the state
sub set_state {
	my $self = shift;
	$self->{state} = shift;
}

#Enable/disable the slider appropriately
sub budget_combo_select {
	my $self = shift;
	
	my $value = $self->{budget_combo}->GetValue();
	
	if ( $value eq 'Budget:' ) {
		$self->{slider}->Enable();
	} else {
		$self->{slider}->Disable();		
	}
	#Update the grid
	$self->on_grid_data_change();
	unless ( $self->{slider}->IsEnabled() ) {
		$self->{grid_solutions}->set_sort_direction(0);
		$self->{grid_solutions}->sortcols(-1);
	}
	#In GTK the scroll bar is too short until the window is resized...
	$self->{grid_solutions}->Refresh();
}

#Called if the grid data changes
sub on_grid_data_change {
	my ($self, $event) = @_;
	#Load the data for the grid for the selected budget
	$self->load_budget_data();
	#Fill the grid using the loaded data
	$self->fill_grid();
	
	if (defined $event) { $event->Skip(); }
	
}

#Load/compute all the data for a given budget 
sub load_budget_data {
	my ($self, $event) = @_;

	unless (defined $self->{state}->{solutions}) {
		if (defined $event) { $event->Skip(); }
		#Should clear some stuff here....
		return;
	}

	#Get the budget mode (all costs or a given cost)
	my $budget_mode = $self->{budget_combo}->GetValue();
	
	my @solutions;
	my $budget; 
	
	#If we are selecting a specific budget
	if ( $budget_mode eq 'Budget:' ) {
		#Get the selected cost
		$budget = $self->{slider}->get_value();
		#And the corresponding solutions
		@solutions = $self->{state}->{solutions}->get_solutions_cost($budget);
	#Or selecting all solutions
	} else {
		@solutions = $self->{state}->{solutions}->get_solutions_all();
	}
	$self->{solutions} = \@solutions;
	$self->{solution_names} = [];
	#load solution specific data
	foreach (@solutions) { 
		#Store the solution name
		push(@{$self->{solution_names}},$_->{name});
		
		#Get the species expenditure for this solution
		my %expenditure = $self->{state}->{solutions}->get_expenditure($_->{name});
		#Get the survival probabilities for this expenditure
		my %survival = $self->{state}->{survival}->get_survival_probability(%expenditure);
		
		#Store it all
		$self->{data}->{$_->{name}}->{value} = \%expenditure;
		$self->{data}->{$_->{name}}->{survival} = \%survival;
		$self->{data}->{$_->{name}}->{cost} = $_->{cost};
		$self->{data}->{$_->{name}}->{type} = 'solution';
	}
	
	#load indice data
	foreach (keys %{+INDICES}) {
		my $lcIndex = lc(INDICES->{$_});
		my %index = %{eval('$self->{state}->{tree}->calc_'."$lcIndex")};
		my %survival = $self->{state}->{survival}->get_survival_probability_budget_index($budget, %index);
		#Store it all
		$self->{data}->{$_}->{value} = \%index;
		$self->{data}->{$_}->{survival} = \%survival;
		$self->{data}->{$_}->{cost} = $budget;
		$self->{data}->{$_}->{type} = 'index';	}
		
	#load summary data		
	foreach (keys %{+SUMMARY}) {
		my $lcFunction = lc(SUMMARY->{$_});
		my %index = %{eval('$self->{state}->{solutions}->get_'."$lcFunction")};
		#my %survival = $self->{state}->{survival}->get_survival_probability_budget_index($budget, %index);
		#Store it all
		$self->{data}->{$_}->{value} = \%index;
#		$self->{data}->{$_}->{survival} = \%survival;
		$self->{data}->{$_}->{cost} = '-';
		$self->{data}->{$_}->{epd} = '-';
		$self->{data}->{$_}->{type} = 'summary';		
	}
	
	#Compute rescaled trees
	foreach (@solutions,keys %{+INDICES}) {
			#Make a copy of the tree
			my $resized_tree = Bio::Phylo::IO->parse(
		   		'-string' => $self->{state}->{tree}->to_newick(),
   				'-format' => 'newick',
				)->first;	
			#Scale the tree
			$resized_tree->scale_survival(%{$self->{data}->{$_}->{survival}});
			#Store it
			$self->{rescaled}->{$_} = $resized_tree;
			$self->{data}->{$_}->{epd} = $resized_tree->calc_tree_length();
	}
	
	if (defined $event) { $event->Skip(); }
	
}

#Fill the grid with precomputed values
sub fill_grid {
	
	my ($self, $event) = @_;

	unless (defined $self->{state}->{solutions}) {
		if (defined $event) { $event->Skip(); }
		#Should clear some stuff here....
		return;
	}

	#Get the number of columns for indices and summary stats
	my $ncols_indices = $self->{"check_index"}->GetValue() * (scalar keys %{+INDICES});
	my $ncols_summary = $self->{"check_summary"}->GetValue() * (scalar keys %{+SUMMARY});
	my $ncols_solutions = $self->{"check_solution"}->GetValue() * (scalar @{$self->{solutions}});

	#Get the number of columns
	my $ncols = $ncols_solutions + $ncols_indices + $ncols_summary;

	#Establish a list of column names
	my @col_labels;		
	if ($ncols_indices) { push(@col_labels, sort(keys %{+INDICES})); }
	if ($ncols_summary) { push(@col_labels, sort(keys %{+SUMMARY})); }
	if ($ncols_solutions) { push(@col_labels, sort(@{$self->{solution_names}})); }
	
	my @species = sort @{$self->{state}->{species}};
		
	#Disable event processing 
	$self->{grid_solutions}->disable_events();

	#Delete all rows & cols and replace with the right number
	$self->{grid_solutions}->DeleteRows(0, $self->{grid_solutions}->GetNumberRows);
	$self->{grid_solutions}->AppendRows(scalar @species + 2 );
	$self->{grid_solutions}->DeleteCols(0, $self->{grid_solutions}->GetNumberCols);
	$self->{grid_solutions}->AppendCols($ncols);

	#Used to get row numbers from species names
	my %species;

	#Set the row labels		
	for (my $index = 0; $index < scalar @species; $index++) {
		#Set the index for adding data
		$species{$species[$index]}=$index;
		#Set the row label
		$self->{grid_solutions}->SetRowLabelValue($index,$species[$index]);
	}
	
	#Set extra row 
	$self->{grid_solutions}->SetRowLabelValue(scalar @species, 'Expected PD');
	$self->{grid_solutions}->SetRowLabelValue(@species + 1, 'Cost');
	
	#Add the data
	
	for (my $index = 0; $index < scalar @col_labels; $index++) {
		my $label = $col_labels[$index];
		my $type =  $self->{data}->{$label}->{type};
		
		#Check whether to plot this column
		if ($self->{"check_$type"}->GetValue() ) {
		
			#Set the column label
			$self->{grid_solutions}->SetColLabelValue($index,$label);
			my $colour = $self->{"check_$type"}->GetForegroundColour();
			my $a = 200;
			my $b = 55;
			my ($r, $g, $b) = (($b*$colour->Red()/255)+$a, ($b*$colour->Green()/255)+$a, ($b*$colour->Blue()/255)+$a);
			
			$colour->Set($r,$g,$b,$colour->Alpha());
			
			
			#Fill each row 
			foreach (@species) {
				$self->{grid_solutions}->SetCellValue($species{$_},$index, sprintf("%.3g", $self->{data}->{$label}->{value}->{$_}));
				$self->{grid_solutions}->SetCellBackgroundColour($species{$_},$index,$colour);
			}
		
			#Indices other than summary stats get epd and cost entered	
			unless ($type eq 'summary') {
				$self->{grid_solutions}->SetCellValue(scalar @species, $index, sprintf("%.3g", $self->{data}->{$label}->{epd}));
				$self->{grid_solutions}->SetCellValue(scalar @species + 1, $index, sprintf("%.3g", $self->{data}->{$label}->{cost}));
			}
			$self->{grid_solutions}->SetCellBackgroundColour(scalar @species,$index,$colour);
			$self->{grid_solutions}->SetCellBackgroundColour(scalar @species + 1,$index,$colour);

		}
	}
	$self->{grid_solutions}->AutoSizeColumns();
	$self->{grid_solutions}->FitInside();
	$self->{grid_solutions}->enable_events();
	$self->{grid_solutions}->select_column(0);
	if (defined $event) { $event->Skip(); }
}


##On selection in the solution grid we must
##* Draw the tree
##* Update the add and remove species listboxes
sub display_selected {
	my $self = shift;
	my $event = shift;

	#Get the selected column 
	my $selected_column = $self->{grid_solutions}->GetSelectedCols();

	unless (defined $selected_column && defined $self->{state}->{solutions} && defined $self->{state}->{tree}) {
		if (defined $event) { $event->Skip(); }		
		#Should clear some stuff here....
		return;
	}

	#Get the column label
	my $solution_name = $self->{grid_solutions}->GetColLabelValue($selected_column);
	
	#Get the actual solution object
	my $solution = $self->get_selected_solution();

	#Check if this tree has been plotted, if so we have nothing to do
	if (defined $self->{last_drawn_solution} && defined $solution && $self->{last_drawn_solution} == $solution ) { 
		return; 
	}
	$self->{last_drawn_solution} = $solution;
	
	#The currently selected species (will try and retain this selection)
	my $add_selected = $self->{combo_add}->GetStringSelection();
	my $del_selected = $self->{combo_del}->GetStringSelection();

	#Clear the combo boxes
	$self->{combo_add}->Clear();
	$self->{combo_del}->Clear();
	$self->{combo_add}->SetValue('');
	$self->{combo_del}->SetValue('');
	
	#Disable the buttons
	$self->{button_add}->Disable();
	$self->{button_del}->Disable();

	#If we have a solution we fill and enable the greedy navigation combos
	if (defined $solution) {
		my $found = 0;
		foreach (@{$solution->{increment}}) { 
			$self->{combo_add}->Append($_);
			if ($_ eq $add_selected) {$self->{combo_add}->SetSelection($self->{combo_add}->GetCount()-1); $found = 1; }
		}
		unless ($found) {$self->{combo_add}->SetSelection(0); }
		
		$found = 0;
		foreach (@{$solution->{decrement}}) { 
			$self->{combo_del}->Append($_);
			if ($_ eq $del_selected) {$self->{combo_del}->SetSelection($self->{combo_del}->GetCount()-1); $found = 1; }
		}
		unless ($found) {$self->{combo_del}->SetSelection(0); }
		
		if ($self->{combo_add}->GetCount() ) { $self->{button_add}->Enable(); }
		if ($self->{combo_del}->GetCount() ) { $self->{button_del}->Enable(); }
	}
	
	#Remove the drawn tree
	$self->{wxtree}->set_tree();

	my $tree = $self->{rescaled}->{$solution_name};
	
	#If we have a non-zero tree draw it
	if (defined $tree && $self->{data}->{$solution_name}->{epd}) {
		$self->{wxtree}->set_tree($tree);
	}
	if (defined $event) { $event->Skip(); }
}

#Get the solution currently selected
sub get_selected_solution {
	my $self = shift;
	
	my $selection = $self->{grid_solutions}->GetSelectedCols();
	
	unless (defined $selection) { return undef; }

	#Get the solution name
	my $solution_name = $self->{grid_solutions}->GetColLabelValue($selection);

	#Check all solutions against the name
	foreach (@{$self->{solutions}}) {
		if ($_->{name} eq $solution_name) { return $_; }
	}
	return undef;
}

#Remove a species from the greedy set
sub greedy_deletion {
	my $self = shift;
	
	#Get the selected solution
	my $solution = $self->get_selected_solution();
	unless ($solution) { return; }
	
	my @species_in = (@{$solution->{set}});
	my $deletion = $self->{combo_del}->GetStringSelection();
	my @species_out;
	foreach (@{$solution->{set}}) {
		unless ($_ eq  $deletion) { push(@species_out,$_); }
	}
	
	$self->select_set(@species_out);
}

#Add a species to the greedy set
sub greedy_addition {
	my $self = shift;
	
	#Get the selected solution
	my $solution = $self->get_selected_solution();
	unless ($solution) { return; }
	
	my @species_out = (@{$solution->{set}}, $self->{combo_add}->GetStringSelection());
	$self->select_set(@species_out);
}

#Select a set of species
sub select_set {
	my $self = shift;
	my @species = @_;
	$self->{grid_solutions}->disable_events();
	#Set the slider
	if ($self->{slider}->IsEnabled() ) { 
		$self->{slider}->set_value($self->{state}->{solutions}->get_cost_set(@species));
		#Update the list of solutions
		$self->on_grid_data_change();
	}
	
	for (my $index = 0; $index < scalar @{$self->{solutions}}; $index++) {
		if (_lists_equal($self->{solutions}->[$index]->{set},\@species)) {
			$self->{grid_solutions}->enable_events();
			$self->{grid_solutions}->select_column_by_name($self->{solutions}->[$index]->{name});
			return;
		}
	}
}


#Update the slider and grid -> the solutions may have changed
sub update {

	my $self = shift;
	
	if ($self->{block_update}) { return; }
	
	unless (defined $self->{state}->{solutions}) {
		#Should clear some stuff here....
		return;
	}

	#If the tree and solutions haven't changed we have nothing to do.
	if (defined $self->{last_tree} && defined $self->{last_solution} && 
	    $self->{last_tree}==$self->{state}->{tree} && 
	    $self->{last_solutions} == $self->{state}->{solutions}) { return; }
	
	#Set the slider value
	my %cost_pd = $self->{state}->{solutions}->get_cost_pd();
	$self->{costs} =  [sort {$a <=> $b} (keys %cost_pd)];
	$self->{slider}->set_ticks(@{$self->{costs}});	

	#redo the grid
	$self->on_grid_data_change();
	
	$self->{last_tree} = $self->{state}->{tree};
	$self->{last_solutions} = $self->{state}->{solutions};

}


#Check if the first argument is equal to any of the subsequent arguments
sub _is_in_list {
	my $check = shift;
	foreach (@_) {
		if ($_ eq $check) { return 1; }
	}
	return 0;
}

#Check if two lists of species are equal
sub _lists_equal {
	my @list1 = @{shift(@_)};
	my @list2 = @{shift(@_)};
	
	unless (scalar @list1 == scalar @list2) { return 0; }
	
	my %hash;
	foreach (@list1) { $hash{$_} = 1; }
	foreach (@list2) {
		unless (defined $hash{$_}) { return 0; }
		delete $hash{$_};
	}
	if (scalar keys %hash) { return 0; }
	return 1;
	
}

1;
